package org.jeecg.common.rc.aop.dicttrans.classutil;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import org.jeecg.common.util.RefUtil;
import lombok.Data;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import org.apache.commons.lang3.tuple.Pair;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 字节码工具类
 *
 * @author zjarlin
 * @since 2023/01/12
 */
public class ByteBuddyUtil {


    @SneakyThrows
    public static Object genChildObjectRecursion(Object o, Function<Object, List<NeedAddInfo>> getNeedAddInfoFun) {
        List<NeedAddInfo> needAddFields = getNeedAddInfoFun.apply(o);
        needAddFields=needAddFields.stream().distinct().collect(Collectors.toList());
        if (CollUtil.isEmpty(needAddFields)) {
            return o;
        }

        Class<?> aClass = o.getClass();
        DynamicType.Builder<?> subclass = new ByteBuddy().subclass(aClass);

        for (NeedAddInfo e : needAddFields) {
            String fieldName = e.getFieldName();
            Class<?> type = e.getType();
            subclass = subclass.defineProperty(fieldName, type);
        }


        Class<?> loaded = subclass.make().load(aClass.getClassLoader()).getLoaded();
        Object o1 = loaded.newInstance();


        Field[] fields = ReflectUtil.getFields(aClass);
        List<Field> collect = Arrays.stream(fields).filter(e -> RefUtil.isNonNullField(o1, e)).collect(Collectors.toList());
        int size = collect.size();
        Arrays.stream(fields)
                .forEach(e -> {
                    Object fieldValue = ReflectUtil.getFieldValue(o, e);
                    if (RefUtil.isObjectField(o, e)) {
                        Object afterObject = genChildObjectRecursion(fieldValue, getNeedAddInfoFun);
                        ReflectUtil.setFieldValue(o, e, afterObject);
                    } else if (RefUtil.isCollectionField(e)) {
                        Collection fieldValue1 = (Collection) fieldValue;
                        if (CollUtil.isNotEmpty(fieldValue1)) {
                            Object next = fieldValue1.iterator().next();
                            Object o2 = genChildObjectRecursion(next, getNeedAddInfoFun);
                            Object collect3 = fieldValue1.stream().map(x -> Convert.convert(o2.getClass(), x)).collect(Collectors.toList());
                            ReflectUtil.setFieldValue(o, e, collect3);
                        }

                    }
                });
        BeanUtil.copyProperties(o, o1);

        return o1;
    }


    @SneakyThrows
    public static Class<?> genChildObjectRecursion(Class<?> claz, Function<Object, List<NeedAddInfo>> getNeedAddInfoFun) {
        Object o = claz.newInstance();
        Object o3 = genChildObjectRecursion(o, getNeedAddInfoFun);
        return o3.getClass();
    }


    public static <T> Class<? extends T> genChildClassRecursion(Class<T> tClass, Function<Class<?>, List<NeedAddInfo>> getNeedAddInfoFun) {
        List<NeedAddInfo> needAddFields = getNeedAddInfoFun.apply(tClass);
        if (CollUtil.isEmpty(needAddFields)) {
            return tClass;
        }

        DynamicType.Builder<T> subclass = new ByteBuddy().subclass(tClass);
        for (NeedAddInfo e : needAddFields) {
            String fieldName = e.getFieldName();
            Object rootObject = e.getRootObject();
            Class<?> type = e.getType();
            Boolean recur = e.getRecur();
            Boolean isT = e.getIsT();

            Boolean isColl = e.getIsColl();

            if (recur && isT) {
                Class<?> aClass = genChildClassRecursion(type, getNeedAddInfoFun);
                type = aClass;
            }
            subclass = subclass.defineProperty(fieldName, type);
        }

        return subclass.make().load(tClass.getClassLoader()).getLoaded();
    }

    public static <T> Class<? extends T> genChildClass(List<String> needAddFields, final Class<T> superClass) {
        return genChildClass(needAddFields, superClass, String.class);
    }

    public static <T> Class<? extends T> genChildClass(List<String> needAddFields, final Class<T> superClass, final Type type) {
        if (CollUtil.isEmpty(needAddFields)) {
            return superClass;
        }
        DynamicType.Builder<T> subclass = new ByteBuddy().subclass(superClass);
        DynamicType.Builder<T> tOptional = null;
        for (String needAddField : needAddFields) {
            //defineProperty会生成GetterAndSetter
            subclass = subclass.defineProperty(needAddField, type);
        }
        return subclass.withToString()
                .make().load(superClass.getClassLoader()).getLoaded();
    }

    public static <T> Class<? extends T> genChildClassWithPair(Set<Pair<String, ? extends Class<?>>> needAddFields, final Class<T> superClass) {
        if (ArrayUtil.isEmpty(needAddFields)) {
            return superClass;
        }
        DynamicType.Builder<T> subclass = new ByteBuddy().subclass(superClass);
        DynamicType.Builder<T> tOptional = null;
        for (Pair<String, ? extends Class<?>> item : needAddFields) {
            String key = item.getKey();
            Type value = item.getValue();
            subclass = subclass.defineProperty(key, value);
        }
        Class<? extends T> loaded = subclass.withToString()
                //                .withHashCodeEquals()
                .make().load(superClass.getClassLoader()).getLoaded();
        return loaded;
    }

    @SneakyThrows
    private static <T> Map<TypeDescription, File> genChildClass2(List<String> needAddFields, final Class<T> superClass) {
        String s = superClass.getSimpleName() + "$Pro";
        String packagePath = ClassUtil.getPackagePath(ByteBuddyUtil.class);

        File file = new File(packagePath + File.separator + s);
        DynamicType.Builder<T> subclass = new ByteBuddy().subclass(superClass);

        for (String needAddField : needAddFields) {
            subclass = subclass.defineProperty(needAddField, String.class);
        }
        Map<TypeDescription, File> typeDescriptionFileMap = subclass.withToString().withHashCodeEquals().make().load(superClass.getClassLoader()).saveIn(file);
        return typeDescriptionFileMap;
    }

    @Data
    public static
    class NeedAddInfo {
        private Object rootObject;
        private String fieldName;
        /**
         * 需要递归创建
         */
        private Boolean recur;
        /**
         * 实体
         */
        private Boolean isT;
        /**
         * 集合
         */
        private Boolean isColl;
        private Class<?> type;

    }

}
